﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Drawing2D;

namespace Vortex.Tools.FormControls {
	
	public partial class GradientEditControl : Control {
		public const float MIN_POSITION = 0.0f;
		public const float MAX_POSITION = 1.0f;

		private Color _backgroundGridColor1 = Color.White;
		private Color _backgroundGridColor2 = Color.LightGray;
		

		private List<GradientStopPoint> _points;
		private int _backgroundCellSize = 8;
		private int _controlPointSize = 10;


		private int _lockedPoint = -1;

		public GradientEditControl() {			
			InitializeComponent();
			Points = null;
			SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
			SetStyle(ControlStyles.AllPaintingInWmPaint, true);
		}

		private void RaiseChangeEvent() {
			if (null != Changed) {
				Changed(this, new EventArgs()); 
			}
			Refresh();
		}

		#region Input Control

		protected override void OnDoubleClick(EventArgs ea) {
			MouseEventArgs e = ea as MouseEventArgs;
			if (e.Button == MouseButtons.Left) {
				int pointIndex = FindPoint(e.Location);
				if (null != ColorPointChange && pointIndex >= 0) {
					GradientStopPoint stop = _points[pointIndex];
					
					ColorChangeEventArgs args = new ColorChangeEventArgs(stop.Color);
					ColorPointChange(this, args);
					stop.Color = Color.FromArgb(
						(int)_points[pointIndex].Color.A,
						args.Color
					);

					//store stop point
					_points[pointIndex] = stop;

					//refresh control
					RaiseChangeEvent();
				}
			}
		}

		protected override void OnMouseMove(MouseEventArgs e) {
			if (_lockedPoint >= 0) {
				//read current point
				GradientStopPoint stopPoint = _points[_lockedPoint];
				stopPoint.Color = Color.FromArgb(
					Clamp((int)(((float)(Size.Height - e.Y) / (float)Size.Height) * 255.0f), 0, 255),
					stopPoint.Color
				);

				if (_lockedPoint > 0 && _lockedPoint < _points.Count - 1) {
					float minPosition = _points[_lockedPoint - 1].Position;
					float maxPosition = _points[_lockedPoint + 1].Position;
					float position = (float)e.X / (float)Size.Width;

					stopPoint.Position = Math.Max(Math.Min(position, maxPosition), minPosition);
				}

				//save point
				_points[_lockedPoint] = stopPoint;

				//update control
				RaiseChangeEvent();
			}
		}

		private int Clamp(int value, int min, int max) {
			return Math.Min(Math.Max(value, min), max);
		}

		protected override void OnMouseDown(MouseEventArgs e) {
			int clickedPoint = FindPoint(e.Location);
			if (e.Button == MouseButtons.Left) {
				if (clickedPoint < 0) {
					clickedPoint = InsertPointAt(e.Location);
					RaiseChangeEvent();
				}

				_lockedPoint = clickedPoint;
			}
			if (e.Button == MouseButtons.Right) {
				if (clickedPoint > 0 && clickedPoint < _points.Count - 1) {
					_points.RemoveAt(clickedPoint);
					RaiseChangeEvent();
				}
			}
		}

		private int InsertPointAt(Point point) {
			//calculate relative point
			PointF relativePoint = new PointF(
				Math.Min(Math.Max((float)point.X / (float)Size.Width, 0), 1),
				Math.Min(Math.Max((float)(Size.Height - point.Y) / (float)Size.Height, 0), 1)
			);

			for (int n = 1; n < _points.Count; ++n) {
				GradientStopPoint stopPoint = _points[n];
				if (stopPoint.Position > relativePoint.X) {
					//do insert at this point
					Color blendedColor = BlendColors(_points[n - 1], _points[n], relativePoint.X);
					blendedColor = Color.FromArgb((int)(relativePoint.Y * 255.0f), blendedColor);
					_points.Insert(n, new GradientStopPoint(blendedColor, relativePoint.X));
					return n;
				}
			}

			return -1;
		}

		private Color BlendColors(GradientStopPoint stop1, GradientStopPoint stop2, float position) {
			float stopDifference = (stop2.Position - stop1.Position);
			float factor = stopDifference == 0 ? 0 : (position - stop1.Position) / stopDifference;
			
			return Color.FromArgb(
				(int)LerpValue(stop1.Color.A, stop2.Color.A, factor),
				(int)LerpValue(stop1.Color.R, stop2.Color.R, factor),
				(int)LerpValue(stop1.Color.G, stop2.Color.G, factor),
				(int)LerpValue(stop1.Color.B, stop2.Color.B, factor)
			);
		}

		private float LerpValue(float val1, float val2, float factor) {			
			return val1 * factor + val2 * (1 - factor);
		}

		protected override void OnMouseUp(MouseEventArgs e) {
			if (e.Button == MouseButtons.Left) {
				_lockedPoint = -1;
			}
		}

		private int FindPoint(Point point) {
			for (int n = 0; n < _points.Count; n++) {
				GradientStopPoint stopPoint = _points[n];

				Point pos = StopToPoint(stopPoint);
				if (Math.Abs(pos.X - point.X) <= ControlPointSize && Math.Abs(pos.Y - point.Y) <= ControlPointSize) {
					return n;
				}
			}

			return -1;
		}

		#endregion

		#region Drawing

		protected override void OnPaint(PaintEventArgs pe) {
			DrawBackground(pe.Graphics);
			DrawGradient(pe.Graphics);
			DrawPolyline(pe.Graphics);
			DrawControlPoints(pe.Graphics);
		}

		private void DrawControlPoints(Graphics graphics) {
			foreach (GradientStopPoint point in Points) {
				//draw point
				DrawPoint(graphics, StopToPoint(point), Color.FromArgb(255, point.Color));
			}
		}

		private void DrawPoint(Graphics graphics, Point pos, Color color) {
			Pen pen1 = new Pen(Color.Black);
			Pen pen2 = new Pen(Color.White);

			//draw outer frame
			Rectangle rect = new Rectangle(pos.X, pos.Y, ControlPointSize * 2 + 1, ControlPointSize * 2 + 1);
			rect.Offset(-ControlPointSize, -ControlPointSize);

			graphics.FillRectangle(new SolidBrush(color), rect);
			graphics.DrawRectangle(pen1, rect);

			//draw inner frame
			rect.Offset(1, 1);
			rect.Width -= 2;
			rect.Height -= 2;
			graphics.DrawRectangle(pen2, rect);
		}

		private void DrawGradient(Graphics graphics) {
			float widthFactor = ClientSize.Width;

			for (int n = 0; n < _points.Count - 1; ++n) {
				GradientStopPoint p1 = _points[n], p2 = _points[n + 1];
				int fromX = (int)(p1.Position * widthFactor);
				int toX = (int)(p2.Position * widthFactor);

				if (toX > fromX) {
					int clientHalfHeight = ClientSize.Height / 2;
					
					LinearGradientBrush brush = new LinearGradientBrush(
						new PointF(fromX - 1, clientHalfHeight),
						new PointF(toX + 1, clientHalfHeight),
						p1.Color,
						p2.Color
					);

					graphics.FillRectangle(brush, fromX, 0, toX - fromX, ClientSize.Height);
				}
			}
		}

		private void DrawBackground(Graphics graphics) {
			int cellSize = BackgroundCellSize;
			int cellCountX = (ClientSize.Width / cellSize + 1) | 1; //ensure step count will be odd
			int cellCountY = (ClientSize.Height / cellSize + 1);

			bool brushFlag = false;
			int linePosY = 0;
			Brush brush1 = new SolidBrush(BackgroundGridColor1), brush2 = new SolidBrush(BackgroundGridColor2);

			for (int n = 0; n < cellCountY; ++n) {
				int linePosX = 0;	
				for (int m = 0; m < cellCountX; ++m ) {
					graphics.FillRectangle(brushFlag ? brush1 : brush2, linePosX, linePosY, cellSize, cellSize);

					linePosX += cellSize;
					brushFlag = !brushFlag;
				}
				linePosY += cellSize;
			}
		}


		private void DrawPolyline(Graphics graphics) {
			List<Point> points = new List<Point>();
			List<Point> points2 = new List<Point>();
			foreach (GradientStopPoint stopPoint in _points) {
				Point p = StopToPoint(stopPoint);
				points.Add(new Point(p.X, p.Y));
				points2.Add(new Point(p.X, p.Y + 1));
			}

			graphics.DrawLines(new Pen(Color.White), points.ToArray());
			graphics.DrawLines(new Pen(Color.Black), points2.ToArray());
		}

		private Point StopToPoint(GradientStopPoint point) {
			return new Point(
				StopPositionToOffsetX(point.Position),
				StopColorToOffsetY(point.Color)
			);
		}

		private int StopPositionToOffsetX(float stopPosition) {
			return (int)Math.Floor(stopPosition * ClientSize.Width);
		}

		private int StopColorToOffsetY(Color color) {
			float alpha = (float)color.A / 255.0f;
			return (int)Math.Floor((1 - alpha) * ClientSize.Height);
		}
		#endregion

		#region Properties

		[Browsable(true)]
		public Color BackgroundGridColor1 {
			get { return _backgroundGridColor1; }
			set { _backgroundGridColor1 = value; Refresh(); }
		}

		[Browsable(true)]
		public Color BackgroundGridColor2 {
			get { return _backgroundGridColor2; }
			set { _backgroundGridColor2 = value; Refresh(); }
		}

		[Browsable(true)]
		public int BackgroundCellSize {
			get { return _backgroundCellSize; }
			set { _backgroundCellSize = Math.Max(Math.Min(value, 100), 2); Refresh(); }
		}

		public int ControlPointSize {
			get { return _controlPointSize; }
			set { _controlPointSize = Math.Max(Math.Min(value, 20), 3); Refresh(); }
		}

		/// <summary>
		/// Gets or sets the gradient stop points.
		/// </summary>
		/// <value>The gradient stop points.</value>
		public GradientStopPoint[] Points {
			get { return _points.ToArray(); }
			set {
				SetPoints(value);
			}
		}

		#endregion

		private void SetPoints(GradientStopPoint[] points) {
			if (null == points || points.Length == 0) {
				points = new GradientStopPoint[] { 
					GradientStopPoint.DEFAULT_START_POINT,
					GradientStopPoint.DEFAULT_END_POINT
				};
			}

			_points = new List<GradientStopPoint>(points);
			
			//check bottom value
			if (_points[0].Position > MIN_POSITION) {
				GradientStopPoint stop = _points[0];
				stop.Position = MIN_POSITION;
				_points.Insert(0, stop);
			}
			
			//check last guard value
			if (_points[_points.Count - 1].Position > MAX_POSITION) {
				GradientStopPoint stop = _points[_points.Count - 1];
				stop.Position = MAX_POSITION;
				_points.Add(stop);
			}

			Refresh();
		}

		#region Event

		[Browsable(true)]
		public event EventHandler Changed;

		[Browsable(true)]
		public event ColorChangeEventHandler ColorPointChange;

		#endregion
	}

}

